#include "inc/st7735.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>
#include "inc/font.h"

spi_device_handle_t spi;

void lcd_cmd(spi_device_handle_t spi, const uint8_t cmd) {
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));
    t.length = 8;
    t.tx_data[0] = cmd;
    t.flags = SPI_TRANS_USE_TXDATA;
    gpio_set_level(PIN_NUM_DC, 0);
    spi_device_transmit(spi, &t);
}

void lcd_data(spi_device_handle_t spi, const uint8_t *data, int len) {
    spi_transaction_t t;
    if (len == 0) return;
    memset(&t, 0, sizeof(t));
    t.length = len * 8;
    t.tx_buffer = data;
    gpio_set_level(PIN_NUM_DC, 1);
    spi_device_transmit(spi, &t);
}

void ST7735_Reset(void) {
    gpio_set_level(PIN_NUM_RST, 0);
    vTaskDelay(pdMS_TO_TICKS(100));
    gpio_set_level(PIN_NUM_RST, 1);
    vTaskDelay(pdMS_TO_TICKS(100));
}

void ST7735_Init(void) {
    esp_err_t ret;
    spi_bus_config_t buscfg = {
        .miso_io_num = PIN_NUM_MISO,
        .mosi_io_num = PIN_NUM_MOSI,
        .sclk_io_num = PIN_NUM_CLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = SOC_SPI_MAXIMUM_BUFFER_SIZE
    };

    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 50 * 1000 * 1000,
        .mode = 3,
        .spics_io_num = PIN_NUM_CS,
        .queue_size = 7,
        .pre_cb = NULL,
        .post_cb = NULL,
    };

    ret = spi_bus_initialize(LCD_HOST, &buscfg, DMA_CHAN);
    ESP_ERROR_CHECK(ret);

    ret = spi_bus_add_device(LCD_HOST, &devcfg, &spi);
    ESP_ERROR_CHECK(ret);

    gpio_set_direction(PIN_NUM_DC, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_BCKL, GPIO_MODE_OUTPUT);

    ST7735_Reset();

    // Initialization sequence for ST7735
    const uint8_t init_cmds[] = {
        0x01, 0, // Software reset
        0x11, 0, // Sleep out
        0xB1, 3, 0x01, 0x2C, 0x2D, // Frame rate control
        0xB2, 3, 0x01, 0x2C, 0x2D, // Frame rate control
        0xB3, 6, 0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D, // Frame rate control
        0xB4, 1, 0x07, // Display inversion control
        0xC0, 3, 0xA2, 0x02, 0x84, // Power control 1
        0xC1, 1, 0xC5, // Power control 2
        0xC2, 2, 0x0A, 0x00, // Power control 3
        0xC3, 2, 0x8A, 0x2A, // Power control 4
        0xC4, 2, 0x8A, 0xEE, // Power control 5
        0xC5, 1, 0x0E, // VCOM control 1
        0x36, 1, 0xC8, // Memory data access control
        0x3A, 1, 0x05, // Set color mode to 16-bit
        0xE0, 16, 0x02, 0x1C, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2D, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10, // Positive gamma correction
        0xE1, 16, 0x03, 0x1D, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10, // Negative gamma correction
        0x29, 0, // Display on
    };

    size_t cmd = 0;
    while (cmd < sizeof(init_cmds)) {
        lcd_cmd(spi, init_cmds[cmd++]);
        int len = init_cmds[cmd++];
        lcd_data(spi, &init_cmds[cmd], len);
        cmd += len;
    }

    // Enable backlight
    gpio_set_level(PIN_NUM_BCKL, 1);

    ST7735_SetRotation(1);
    ST7735_FillScreen(ST7735_BLACK);
}

void ST7735_Init_DMA() {
    spi_bus_config_t buscfg = {
        .miso_io_num = PIN_NUM_MISO,
        .mosi_io_num = PIN_NUM_MOSI,
        .sclk_io_num = PIN_NUM_CLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 4096,
        // .dma_chan = DMA_CHAN  // 显式分配 DMA 通道
    };

    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 80 * 1000 * 1000,  // 提升时钟速度
        .mode = 0,
        .spics_io_num = PIN_NUM_CS,
        .queue_size = 7,
        .flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_NO_DUMMY,
        // .dma_chan = DMA_CHAN
    };

    ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, DMA_CHAN));
    ESP_ERROR_CHECK(spi_bus_add_device(LCD_HOST, &devcfg, &spi));
        gpio_set_direction(PIN_NUM_DC, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_BCKL, GPIO_MODE_OUTPUT);

    ST7735_Reset();

    // Initialization sequence for ST7735
    const uint8_t init_cmds[] = {
        0x01, 0, // Software reset
        0x11, 0, // Sleep out
        0xB1, 3, 0x01, 0x2C, 0x2D, // Frame rate control
        0xB2, 3, 0x01, 0x2C, 0x2D, // Frame rate control
        0xB3, 6, 0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D, // Frame rate control
        0xB4, 1, 0x07, // Display inversion control
        0xC0, 3, 0xA2, 0x02, 0x84, // Power control 1
        0xC1, 1, 0xC5, // Power control 2
        0xC2, 2, 0x0A, 0x00, // Power control 3
        0xC3, 2, 0x8A, 0x2A, // Power control 4
        0xC4, 2, 0x8A, 0xEE, // Power control 5
        0xC5, 1, 0x0E, // VCOM control 1
        0x36, 1, 0xC8, // Memory data access control
        0x3A, 1, 0x05, // Set color mode to 16-bit
        0xE0, 16, 0x02, 0x1C, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2D, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10, // Positive gamma correction
        0xE1, 16, 0x03, 0x1D, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10, // Negative gamma correction
        0x29, 0, // Display on
    };

    size_t cmd = 0;
    while (cmd < sizeof(init_cmds)) {
        lcd_cmd(spi, init_cmds[cmd++]);
        int len = init_cmds[cmd++];
        lcd_data(spi, &init_cmds[cmd], len);
        cmd += len;
    }

    // Enable backlight
    gpio_set_level(PIN_NUM_BCKL, 1);

    ST7735_SetRotation(1);
    ST7735_FillScreen(ST7735_BLACK);
}

void ST7735_DrawBitmap_DMA(const uint16_t *bitmap, uint16_t width, uint16_t height) {
    ST7735_SetAddressWindow(0, 0, width - 1, height - 1);
    lcd_cmd(spi, ST7735_RAMWR);

    uint32_t total_pixels = width * height;
    uint32_t max_block_size = 2046; // 4092字节 / 2（每像素2字节）
    uint32_t sent_pixels = 0;

    while (sent_pixels < total_pixels) {
        uint32_t remaining = total_pixels - sent_pixels;
        uint32_t block_size = (remaining > max_block_size) ? max_block_size : remaining;
        
        lcd_data(spi, (uint8_t*)(bitmap + sent_pixels), block_size * sizeof(uint16_t));
        sent_pixels += block_size;
    }
}


void ST7735_SetRotation(uint8_t rotation) {
    lcd_cmd(spi, ST7735_MADCTL);
    switch (rotation) {
        case 0:
            lcd_data(spi, (uint8_t[]){ST7735_MADCTL_MX | ST7735_MADCTL_MY}, 1);
            break;
        case 1:
            lcd_data(spi, (uint8_t[]){ST7735_MADCTL_MY | ST7735_MADCTL_MV}, 1);
            break;
        case 2:
            lcd_data(spi, (uint8_t[]){0x00}, 1);
            break;
        case 3:
            lcd_data(spi, (uint8_t[]){ST7735_MADCTL_MX | ST7735_MADCTL_MV}, 1);
            break;
    }
}

void ST7735_BitMap(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t *bitmap) {
    if ((x >= ST7735_WIDTH) || (y >= ST7735_HEIGHT) || ((x + width - 1) >= ST7735_WIDTH) || ((y + height - 1) >= ST7735_HEIGHT))
        return;

    ST7735_SetAddressWindow(x, y, x + width - 1, y + height - 1);
    lcd_cmd(spi, ST7735_RAMWR);

    for (uint16_t i = 0; i < height; i++) {
        for (uint16_t j = 0; j < width; j++) {
            uint8_t color = bitmap[i * width + j];
            uint8_t data[] = {color >> 8, color & 0xFF};
            lcd_data(spi, data, sizeof(data));
        }
    }
}

void ST7735_SetAddressWindow(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
    uint8_t data[] = {0x00, x0 + xOffset, 0x00, x1 + xOffset};
    lcd_cmd(spi, ST7735_CASET);
    lcd_data(spi, data, sizeof(data));

    data[1] = y0 + yOffset;
    data[3] = y1 + yOffset;
    lcd_cmd(spi, ST7735_RASET);
    lcd_data(spi, data, sizeof(data));

    lcd_cmd(spi, ST7735_RAMWR);
}

void ST7735_DrawChar(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bgColor, const FontDef *font) {
    if ((x >= ST7735_WIDTH) || (y >= ST7735_HEIGHT) || ((x + font->width - 1) >= ST7735_WIDTH) || ((y + font->height - 1) >= ST7735_HEIGHT))
        return;

    ST7735_SetAddressWindow(x, y, x + font->width - 1, y + font->height - 1);
    lcd_cmd(spi, ST7735_RAMWR);

    for (uint8_t i = 0; i < font->height; i++) {
        uint32_t line = font->data[(font == &Font_Custom ? (c - 46) : (c - 32)) * font->height + i];
        for (uint8_t j = 0; j < font->width; j++) {
            if ((line << j) & (font->width > 16 ? 0x80000000 : 0x8000)) {
                uint8_t data[] = { color >> 8, color & 0xFF };
                lcd_data(spi, data, sizeof(data));
            } else {
                uint8_t data[] = { bgColor >> 8, bgColor & 0xFF };
                lcd_data(spi, data, sizeof(data));
            }
        }
    }
}

void ST7735_DrawString(uint16_t x, uint16_t y, const char *str, uint16_t color, uint16_t bgColor, const FontDef *font) {
    while (*str) {
        if (x + font->width > ST7735_WIDTH) {
            x = 0;
            y += font->height;
        }

        if (y + font->height > ST7735_HEIGHT) {
            break;
        }

        ST7735_DrawChar(x, y, *str, color, bgColor, font);
        x += font->width;
        str++;
    }
}

void ST7735_FillScreen(uint16_t color) {
    uint8_t data[2] = {color >> 8, color & 0xFF};
    ST7735_SetAddressWindow(0, 0, ST7735_WIDTH - 1, ST7735_HEIGHT - 1);
    lcd_cmd(spi, ST7735_RAMWR);

    // 使用批量传输
    uint32_t size = ST7735_WIDTH * ST7735_HEIGHT;
    uint8_t buffer[64]; // 每次传输64个像素
    for (int i = 0; i < 64; i += 2) {
        buffer[i] = data[0];
        buffer[i + 1] = data[1];
    }

    while (size > 0) {
        int chunk_size = size > 32 ? 32 : size; // 每次传输32个像素
        lcd_data(spi, buffer, chunk_size * 2);
        size -= chunk_size;
    }
}

void ST7735_DrawRectangle(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color) {
    uint8_t data[2] = {color >> 8, color & 0xFF};
    ST7735_SetAddressWindow(x, y, x + width - 1, y + height - 1);
    for (uint16_t i = 0; i < width * height; i++) {
        lcd_data(spi, data, sizeof(data));
    }
}

void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color) {
    if ((x >= ST7735_WIDTH) || (y >= ST7735_HEIGHT))
        return;

    ST7735_SetAddressWindow(x, y, x, y);
    uint8_t data[2] = {color >> 8, color & 0xFF};
    lcd_data(spi, data, sizeof(data));
}

